﻿using System.Collections.Generic;

namespace Raven
{
    public class MemoryRecord
    {
        //records the time the opponent was last sensed (seen or heard). This
        //is used to determine if a bot can 'remember' this record or not. 
        //(if CurrentTime() - m_dTimeLastSensed is greater than the bot's
        //memory span, the data in this record is made unavailable to clients)
        public Fix timeLastSensed;

        //it can be useful to know how long an opponent has been visible. This 
        //variable is tagged with the current time whenever an opponent first becomes
        //visible. It's then a simple matter to calculate how long the opponent has
        //been in view (CurrentTime - fTimeBecameVisible)
        public Fix timeBecameVisible;

        //it can also be useful to know the last time an opponent was seen
        public Fix timeLastVisible;

        //a vector marking the position where the opponent was last sensed. This can
        // be used to help hunt down an opponent if it goes out of view
        public Vector2 lastSensedPosition;

        //set to true if opponent is within the field of view of the owner
        public bool withinFOV;

        //set to true if there is no obstruction between the opponent and the owner, 
        //permitting a shot.
        public bool shootable;


        public MemoryRecord()
        {
            timeLastSensed = -999;
            timeBecameVisible = -999;
            timeLastVisible = 0;
            withinFOV = false;
            shootable = false;
        }
    };

    public class RavenSensoryMemory
    {
        private RavenBot _owner;

        private Dictionary<RavenBot, MemoryRecord> _memoryMap = new Dictionary<RavenBot, MemoryRecord>();

        private Fix _memorySpan;

        private void MakeNewRecordIfNotAlreadyPresent(RavenBot pOpponent)
        {
            //else check to see if this Opponent already exists in the memory. If it doesn't,
            //create a new record
            if (!_memoryMap.ContainsKey(pOpponent))
            {
                _memoryMap[pOpponent] = new MemoryRecord();
            }
        }

        public RavenSensoryMemory(RavenBot owner, Fix MemorySpan)
        {
            _owner = owner;
            _memorySpan = MemorySpan;
        }

        //this method is used to update the memory map whenever an opponent makes
        //a noise
        public void UpdateWithSoundSource(RavenBot pNoiseMaker)
        {
            //make sure the bot being examined is not this bot
            if (_owner != pNoiseMaker)
            {
                //if the bot is already part of the memory then update its data, else
                //create a new memory record and add it to the memory
                MakeNewRecordIfNotAlreadyPresent(pNoiseMaker);

                MemoryRecord info = _memoryMap[pNoiseMaker];

                //test if there is LOS between bots 
                if (_owner.GetWorld().isLOSOkay(_owner.Pos(), pNoiseMaker.Pos()))
                {
                    info.shootable = true;

                    //record the position of the bot
                    info.lastSensedPosition = pNoiseMaker.Pos();
                }
                else
                {
                    info.shootable = false;
                }

                //record the time it was sensed
                info.timeLastSensed = _owner.GetWorld().GetTimer().currentTime;
            }
        }

        //this removes a bot's record from memory
        public void RemoveBotFromMemory(RavenBot pOpponent)
        {
            if (_memoryMap.ContainsKey(pOpponent))
            {
                _memoryMap.Remove(pOpponent);
            }
        }

        //this method iterates through all the opponents in the game world and 
        //updates the records of those that are in the owner's FOV
        public void UpdateVision()
        {
            //for each bot in the world test to see if it is visible to the owner of
            //this class
            List<RavenBot> bots = _owner.GetWorld().GetAllBots();
            foreach (var curBot in bots)
            {
                //make sure the bot being examined is not this bot
                if (_owner != curBot)
                {
                    //make sure it is part of the memory map
                    MakeNewRecordIfNotAlreadyPresent(curBot);

                    //get a reference to this bot's data
                    MemoryRecord info = _memoryMap[curBot];

                    //test if there is LOS between bots 
                    if (_owner.GetWorld().isLOSOkay(_owner.Pos(), curBot.Pos()))
                    {
                        info.shootable = true;

                        //test if the bot is within FOV
                        if (Geometry.isSecondInFOVOfFirst(_owner.Pos(),
                            _owner.Facing(),
                            curBot.Pos(),
                            _owner.FieldOfView()))
                        {
                            info.timeLastSensed = _owner.GetWorld().GetTimer().currentTime;
                            info.lastSensedPosition = curBot.Pos();
                            info.timeLastVisible = _owner.GetWorld().GetTimer().currentTime;

                            if (info.withinFOV == false)
                            {
                                info.withinFOV = true;
                                info.timeBecameVisible = info.timeLastSensed;
                            }
                        }

                        else
                        {
                            info.withinFOV = false;
                        }
                    }

                    else
                    {
                        info.shootable = false;
                        info.withinFOV = false;
                    }
                }
            } //next bot
        }

        public bool isOpponentShootable(RavenBot pOpponent)
        {
            if (pOpponent == null || !_memoryMap.ContainsKey(pOpponent))
            {
                return false;
            }

            return _memoryMap[pOpponent].shootable;
        }

        public bool isOpponentWithinFOV(RavenBot pOpponent)
        {
            if (pOpponent == null || !_memoryMap.ContainsKey(pOpponent))
            {
                return false;
            }

            return _memoryMap[pOpponent].withinFOV;
        }

        public Vector2 GetLastRecordedPositionOfOpponent(RavenBot pOpponent)
        {
            Utility.Assert(_memoryMap.ContainsKey(pOpponent),
                "< Raven_SensoryMemory::GetLastRecordedPositionOfOpponent>: Attempting to get position of unrecorded bot");

            return _memoryMap[pOpponent].lastSensedPosition;
        }

        public Fix GetTimeOpponentHasBeenVisible(RavenBot pOpponent)
        {
            if (!_memoryMap.ContainsKey(pOpponent))
            {
                return 0;
            }

            return _owner.GetWorld().GetTimer().currentTime - _memoryMap[pOpponent].timeBecameVisible;
        }

        public Fix GetTimeSinceLastSensed(RavenBot pOpponent)
        {
            if (!_memoryMap.ContainsKey(pOpponent))
            {
                return 0;
            }

            return _owner.GetWorld().GetTimer().currentTime - _memoryMap[pOpponent].timeLastSensed;
        }

        public Fix GetTimeOpponentHasBeenOutOfView(RavenBot pOpponent)
        {
            if (pOpponent == null || !_memoryMap.ContainsKey(pOpponent))
            {
                return Fix.MaxValue;
            }

            return _owner.GetWorld().GetTimer().currentTime - _memoryMap[pOpponent].timeLastVisible;
        }

        //this method returns a list of all the opponents that have had their
        //records updated within the last m_dMemorySpan seconds.
        public LinkedList<RavenBot> GetListOfRecentlySensedOpponents()
        {
            //this will store all the opponents the bot can remember
            LinkedList<RavenBot> opponents = new LinkedList<RavenBot>();

            Fix currentTime = _owner.GetWorld().GetTimer().currentTime;

            foreach (var v in _memoryMap)
            {
                //if this bot has been updated in the memory recently, add to list
                if ((currentTime - v.Value.timeLastSensed) <= _memorySpan)
                {
                    opponents.AddLast(v.Key);
                }
            }

            return opponents;
        }

        public void ClearMemory()
        {
            _memoryMap.Clear();
        }
    }
}